home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac Power 1997 December
/
MACPOWER-1997-12.ISO.7z
/
MACPOWER-1997-12.ISO
/
AMUG
/
PROGRAMMING
/
Raven 1.2.sit
/
Raven 1.2
/
Source
/
Foundation
/
Common
/
ZSimpleAllocator.cpp
< prev
next >
Wrap
Text File
|
1997-07-26
|
13KB
|
512 lines
/*
* File: ZSimpleAllocator.cpp
* Summary: Fast general purpose allocator.
* Written by: Jesse Jones
*
* Copyright ゥ 1997 Jesse Jones.
* For conditions of distribution and use, see copyright notice in ZTypes.h
*
* Change History (most recent first):
*
* <2> 6/20/97 JDJ Huge block allocation adjusts mHeapSize.
* <1> 1/28/97 JDJ Created
*/
#include <ZSimpleAllocator.h>
#include <ZDebug.h>
#include <ZExceptions.h>
#include <ZMemUtils.h>
#include <ZUnitTest.h>
//-----------------------------------
// Constants
// On a 601 doubles should be on 8-byte boundaries. On a 604 doubles only need to
// be on 4-byte boundaries. Since we don't want to give 601 users the shaft and
// there's no telling what future processors will require we'll align blocks to
// 8-byte boundaries on a PPC.
//
#if powerc
const long kAlignTo = 8;
#else
const long kAlignTo = 4; // this is fine on 68K's (and NewPtr only aligns to 4-bytes on 68030's)
#endif
const long kBlockOverhead = sizeof(long) + sizeof(SPool*);
const long kPoolOverhead = sizeof(ulong) + 2*sizeof(SPool*) + sizeof(long);
const ulong kMaxBlockSize = 0x3FFFFFFFL; // maximum size for a block in a pool
// ===================================================================================
// struct SBlock
// ===================================================================================
struct SBlock {
unsigned free : 1; // set if the block is no longer in use
unsigned huge : 1; // set if the block was allocated with NewPtr (ie not part of a pool)
unsigned size : 30; // size of the entire block (includes header)
SPool* pool; // header has to be 8 bytes so we'll remember our pool
Byte data[8]; // variable length user data
};
// ===================================================================================
// struct SPool
// ===================================================================================
struct SPool {
ulong marker; // special tag used for sanity checking
SPool* nextPool;
SPool* prevPool;
ulong size; // size of the entire pool (includes header)
SBlock data[1]; // pools start with one block which then gets subdivided
};
// ===================================================================================
// Internal Functions
// ===================================================================================
//---------------------------------------------------------------
//
// GetPoolEnd
//
//---------------------------------------------------------------
inline SBlock* GetPoolEnd(SPool* pool)
{
ASSERT(pool != nil);
SBlock* end = reinterpret_cast<SBlock*>((char*) pool + pool->size);
return end;
}
//---------------------------------------------------------------
//
// GetNextBlock
//
//---------------------------------------------------------------
inline SBlock* GetNextBlock(SBlock* block)
{
ASSERT(block != nil);
ASSERT(!block->huge); // doesn't make sense to call GetNextBlock on a huge block
ASSERT(block->size < 16*1024L*1024L);
SBlock* nextBlock = reinterpret_cast<SBlock*>((char*) block + block->size);
return nextBlock;
}
#pragma mark -
// ===================================================================================
// class TSimpleAllocator
// ===================================================================================
//---------------------------------------------------------------
//
// TSimpleAllocator::~TSimpleAllocator
//
//---------------------------------------------------------------
TSimpleAllocator::~TSimpleAllocator()
{
SPool* pool = mFirstPool;
while (pool != nil) {
SPool* next = pool->nextPool;
DisposePtr((Ptr) pool);
pool = next;
}
}
//---------------------------------------------------------------
//
// TSimpleAllocator::TSimpleAllocator
//
//---------------------------------------------------------------
TSimpleAllocator::TSimpleAllocator(ulong initialSize, ulong poolSize, ulong hugeSize)
{
ASSERT(offsetof(SPool, data) % 16 == 0); // make sure block start is maximally aligned
ASSERT(kBlockOverhead % kAlignTo == 0); // so data is aligned
ASSERT(sizeof(SBlock) == kBlockOverhead + 8);
ASSERT(sizeof(SPool) == kPoolOverhead + sizeof(SBlock));
ASSERT(initialSize >= sizeof(SPool));
ASSERT(poolSize >= sizeof(SPool));
ASSERT(hugeSize < 16*1024L*1024L);
mFirstPool = nil; // AllocatePool adds the new pool to the head of the list
mNewPoolSize = poolSize;
mHugeSize = hugeSize > 0 ? hugeSize : mNewPoolSize/4;
mHeapSize = 0;
mPoolCount = -1; // initial pool doesn't count
mLastSplitBlock = nil;
mLastEnd = nil;
mFirstPool = this->AllocatePool(initialSize);
ThrowIfNil(mFirstPool);
}
//---------------------------------------------------------------
//
// TSimpleAllocator::Allocate
//
//---------------------------------------------------------------
void* TSimpleAllocator::Allocate(ulong bytes)
{
SBlock* block = nil;
bytes = (bytes + kAlignTo - 1) & ~(kAlignTo - 1);
bytes += kBlockOverhead;
// If it's a huge block try using NewPtr.
if (bytes >= mHugeSize || bytes > kMaxBlockSize) {
block = this->AllocateFromHeap(bytes);
if (block != nil)
mHeapSize += block->size;
}
// If the bytes isn't too large we can try to allocate it in one
// of our pools.
if (bytes <= kMaxBlockSize) {
// If the block we last split is large enough use that.
if (block == nil && mLastSplitBlock != nil && mLastSplitBlock->size >= bytes)
block = this->AllocateFromBlock(mLastSplitBlock, mLastEnd, bytes);
// Loop through each pool and try to find a free block that's
// large enough.
if (block == nil)
for (SPool* pool = mFirstPool; pool != nil && block == nil; pool = pool->nextPool)
block = this->AllocateFromPool(pool, bytes);
// Create a new pool.
if (block == nil && bytes < mNewPoolSize - kPoolOverhead) {
SPool* newPool = this->AllocatePool(mNewPoolSize);
if (newPool != nil) {
block = this->AllocateFromPool(newPool, bytes);
ASSERT(block != nil);
}
}
// Create a new block of just the right size.
if (block == nil) {
block = this->AllocateFromHeap(bytes);
if (block != nil)
mHeapSize += block->size;
}
}
if (block != nil)
ASSERT(((long) block->data % kAlignTo) == 0); // make sure user data is properly aligned
return block == nil ? nil : block->data;
}
//---------------------------------------------------------------
//
// TSimpleAllocator::AllocateFromPool
//
//---------------------------------------------------------------
SBlock* TSimpleAllocator::AllocateFromPool(SPool* pool, ulong bytes)
{
ASSERT(pool != nil);
ASSERT(pool->marker == 'JESS');
ASSERT(bytes < 16*1024L*1024L);
SBlock* block = nil;
SBlock* candidate = pool->data;
SBlock* end = GetPoolEnd(pool);
while (candidate < end && block == nil) {
if (candidate->free)
block = this->AllocateFromBlock(candidate, end, bytes);
if (block == nil)
candidate = GetNextBlock(candidate);
}
return block;
}
//---------------------------------------------------------------
//
// TSimpleAllocator::AllocateFromBlock
//
//---------------------------------------------------------------
SBlock* TSimpleAllocator::AllocateFromBlock(SBlock* candidate, const SBlock* end, ulong bytes)
{
ASSERT(candidate != nil);
ASSERT(candidate->free);
ASSERT(!candidate->huge);
ASSERT(candidate->pool != nil);
ASSERT(candidate->pool->marker == 'JESS');
ASSERT(end != nil);
ASSERT(candidate < end);
ASSERT(bytes < 16*1024L*1024L);
SBlock* block = nil;
mLastSplitBlock = nil;
mLastEnd = nil;
// Coalesce all the adjacent freed blocks.
SBlock* nextBlock = GetNextBlock(candidate);
while (nextBlock < end && nextBlock->free) {
candidate->size += nextBlock->size;
nextBlock = GetNextBlock(candidate);
}
// Use the resulting block or split it in two and use the
// right part.
if (candidate->size >= bytes) {
if (candidate->size >= bytes + sizeof(SBlock)) {
mLastSplitBlock = candidate; // remember this block so we can try it next time
mLastEnd = end;
candidate->size -= bytes;
block = GetNextBlock(candidate);
block->size = bytes;
} else
block = candidate;
}
if (block != nil) {
block->free = false;
block->huge = false;
block->pool = candidate->pool;
}
return block;
}
//---------------------------------------------------------------
//
// TSimpleAllocator::AllocateFromHeap
//
//---------------------------------------------------------------
SBlock* TSimpleAllocator::AllocateFromHeap(ulong bytes)
{
ASSERT(bytes < 16*1024L*1024L);
SBlock* block = reinterpret_cast<SBlock*>(NewPtr(bytes));
if (block != nil) {
block->free = false;
block->huge = true;
block->size = bytes;
block->pool = nil;
}
return block;
}
//---------------------------------------------------------------
//
// TSimpleAllocator::Deallocate
//
//---------------------------------------------------------------
void TSimpleAllocator::Deallocate(void* ptr)
{
if (ptr != nil) {
SBlock* block = reinterpret_cast<SBlock*>((char*) ptr - kBlockOverhead);
ASSERT(!block->free);
ASSERT(((long) block->data % kAlignTo) == 0);
block->free = true;
if (block->huge) {
ASSERT(block->pool == nil);
mHeapSize -= block->size;
DisposePtr((Ptr) block);
ASSERT(MemError() == noErr);
} else {
ASSERT(block->pool != nil);
ASSERT(block->pool->marker == 'JESS');
}
}
}
//---------------------------------------------------------------
//
// TSimpleAllocator::AllocatePool
//
//---------------------------------------------------------------
SPool* TSimpleAllocator::AllocatePool(ulong size)
{
SPool* pool = reinterpret_cast<SPool*>(NewPtr(size));
if (pool != nil) {
pool->marker = 'JESS';
pool->nextPool = mFirstPool;
pool->prevPool = nil;
pool->size = size;
if (mFirstPool != nil)
mFirstPool->prevPool = pool;
mFirstPool = pool;
pool->data[0].free = true;
pool->data[0].huge = false;
pool->data[0].pool = pool;
pool->data[0].size = size - kPoolOverhead;
mPoolCount++;
mHeapSize += pool->data[0].size;
}
return pool;
}
//---------------------------------------------------------------
//
// TSimpleAllocator::GetBlockSize
//
//---------------------------------------------------------------
ulong TSimpleAllocator::GetBlockSize(const void* ptr) const
{
ASSERT(ptr != nil);
SBlock* block = reinterpret_cast<SBlock*>((char*) ptr - kBlockOverhead);
ASSERT(!block->free);
ulong bytes = block->size - kBlockOverhead;
return bytes;
}
//---------------------------------------------------------------
//
// TSimpleAllocator::GetTotalBlockSize
//
//---------------------------------------------------------------
ulong TSimpleAllocator::GetTotalBlockSize(const void* ptr) const
{
ASSERT(ptr != nil);
SBlock* block = reinterpret_cast<SBlock*>((char*) ptr - kBlockOverhead);
ASSERT(!block->free);
return block->size;
}
//---------------------------------------------------------------
//
// TSimpleAllocator::ValidateBlock
//
//---------------------------------------------------------------
#if DEBUG
void TSimpleAllocator::ValidateBlock(const void* ptr) const
{
ASSERT(ptr != nil);
SBlock* block = reinterpret_cast<SBlock*>((char*) ptr - kBlockOverhead);
ASSERT(!block->free);
ASSERT(block->size >= sizeof(SBlock));
ASSERT(((long) block->data % kAlignTo) == 0);
if (block->huge) {
ASSERT(block->pool == nil);
ulong size = (ulong) GetPtrSize((Ptr) block);
ASSERT(MemError() == noErr); // see if the Memory Manager thinks the pointer is valid
if (size <= kMaxBlockSize)
ASSERT(size == block->size);
} else {
ASSERT(block->pool != nil);
ASSERT(block->pool->marker == 'JESS');
}
}
#endif
//---------------------------------------------------------------
//
// TSimpleAllocator::ValidateHeap
//
//---------------------------------------------------------------
#if DEBUG
void TSimpleAllocator::ValidateHeap(BlockValidateHook hook, void* refCon) const
{
for (SPool* pool = mFirstPool; pool != nil; pool = pool->nextPool) {
ASSERT(pool->marker == 'JESS');
ASSERT(pool->size >= sizeof(SPool));
SBlock* end = GetPoolEnd(pool);
SBlock* block = pool->data;
while (block < end) {
ASSERT(block->pool == pool);
ASSERT(!block->huge);
ASSERT(((long) block->data % kAlignTo) == 0);
ASSERT(block->size >= sizeof(SBlock));
ASSERT(block->size < pool->size);
if (!block->free && hook != nil)
(*hook)(block->data, (long) (block->size - kBlockOverhead), refCon);
block = GetNextBlock(block);
}
}
}
#endif
#pragma mark -
// ===================================================================================
// Unit Test
// ===================================================================================
//---------------------------------------------------------------
//
// TestSimpleAllocator
//
// See TAllocator::TestAllocator for allocator comparisons.
//
//---------------------------------------------------------------
#if DEBUG
static void TestSimpleAllocator()
{
TSimpleAllocator heap1(64*1024L, 32*1024L, 16*1024);
TSimpleAllocator heap2(64*1024L, 32*1024L, 16*1024);
TSimpleAllocator heap3(64*1024L, 32*1024L, 16*1024);
TAllocator::TestAllocator(heap1, heap2, heap3);
}
static TUnitTestRegistrar sSimpleAllocatorReg("Simple Allocator", TestSimpleAllocator);
#endif // DEBUG